<?php
	
	namespace Aspic\Session;
	
	use Aspic as A;
	use Aspic\File\File;
	
	abstract class AbstractSession extends A\Std {
		use A\Preset\OptionTrait {
			__construct as optionTraitInit;
		}
		
		const SESSION_DATA_INDEX = '__SESSION__';
		
		protected $sessId;
		protected $creationTime;
		protected $hasBeenModified = false;
		
		protected $options;
		
		public function __construct($lifeTime = 86400, array $options = array()) {
			parent::__construct();
			
			$this->optionTraitInit();
			
			$this->lifeTime = $lifeTime;
			
			$this->cookieName = 'PHPSESSID';
			$this->httpOnly = true;
			$this->password = ''; // salt for generation of sessions ids
			$this->navigatorCheck = true;
			$this->langsCheck = false;
			$this->ipCheck = false;
			$this->cleanInterval = 86400;
			
			$defaultOptions = array(
				'lifeTime' => $lifeTime, 
				'cookieName' => 'PHPSESSID', 
				'httpOnly' => true, 
				'password' => '', // salt for generation of sessions ids
				'navigatorCheck' => true, 
				'langsCheck' => false, 
				'ipCheck' => false, 
				'cleanInterval' => 86400, 
			);
			
			$this->options = array_merge($defaultOptions, $options);
			
			$this->checkClean();
			
			$this->loadSession(A\Util::getIfSet($_COOKIE[$this->cookieName]));
		}
		
		public function __destruct() {
			if($this->hasBeenModified) {
				$data = $this->getCurrentSessionData();
				
				$this->saveSession($this->sessId, $data);
			}
		}
	
		public function &get($var) {
			$var = &parent::get($var);
			
			if(is_object($var)) {
				// Maybe object properties will be updated
				$this->hasBeenModified = true;
			}
			
			return $var;
		}
		
		protected function setVars($vars) {
			$this->hasBeenModified = true;
			
			return parent::setVars($vars);
		}
		
		public function __set($varName,$value) {
			parent::__set($varName, $value);
			
			$this->hasBeenModified = true;
		}
		
		protected function loadSession($sessId = null) {
			if(isset($sessId) AND $this->sessionIdExists($sessId)) {
				$this->sessId = $sessId;
				
				$sessData = $this->getSessionData($this->sessId);
				
				if (is_null($sessData)) {
					$validSession = false;
				}
				else {
					$validSession = $this->isSessionDataValid($this->sessId, $sessData['creationTime'], $sessData['ip'], $sessData['navigator'], $sessData['acceptLanguages'], $sessData['data']);
				}

				if($validSession === false) {
					$this->removeSession($this->sessId);
					$this->newSession();
				}
				else {
					$this->creationTime = $sessData[static::SESSION_DATA_INDEX]['creationTime'];
					$this->_vars = $sessData;
				}
			}
			else {
				$this->newSession();
			}
		}
		
		protected function generateId() {
			do {
				$id = A\Security\Security::getRandomId($this->password);
			}
			while($this->sessionIdExists($id));
			
			return $id;
		}
		
		/**
		* Create a new session (file) as the current session
		*/
		protected function newSession($sessData = array(), $keepCreationTime = false) {
			$id = $this->generateId();
			
			if($keepCreationTime) {
				$time = $this->creationTime;
			}
			else {
				$time = time();
			}
			
			$data[static::SESSION_DATA_INDEX]['creationTime'] = $time;
			$data[static::SESSION_DATA_INDEX]['ip'] = $_SERVER['REMOTE_ADDR'];
			$data[static::SESSION_DATA_INDEX]['navigator'] = $_SERVER["HTTP_USER_AGENT"];
			$data[static::SESSION_DATA_INDEX]['acceptLanguages'] = A\Util::getIfset($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
			$data['data'] = $sessData;
			
			$this->saveSession($id, $data);
			
			$cookieExpire = $time + $this->opt('lifeTime');
			
			setcookie($this->cookieName, $id, $cookieExpire, '/', '', false, true);
			
			$this->sessId = $id;
			$this->_vars = $sessData;
			$this->creationTime = $time;
			
			return $id;
		}
		
		/**
		* Return current session data as array
		*/
		protected function getCurrentSessionData() {
			$sessionInfos = array(
				'creationTime' => $this->creationTime, 
				'ip' => $_SERVER['REMOTE_ADDR'], 
				'navigator' => $_SERVER["HTTP_USER_AGENT"], 
				'acceptLanguages' => $_SERVER["HTTP_ACCEPT_LANGUAGE"], 
			);
			
			$sessionData = array_merge($this->_vars, $sessionInfos);
			
			return $sessionData;
		}
		
		/**
		* Check if a session has expire (from session creation time)
		*/
		protected function hasSessionTimeExpire($sessionCreationTime) {
			return !(($sessionCreationTime + $this->opt('lifeTime')) > time());
		}
		
		/**
		* Check if a session has expire (from session id)
		*/
		protected function hasSessionExpire($sessId) {
			$sessData = $this->getSessionData($sessId);
			
			return $this->hasSessionTimeExpire($sessData[static::SESSION_DATA_INDEX]['creationTime']);
		}
		
		protected function isSessionDataValid($id, $creationTime, $ip, $navigator, $langs) { // false if invalid and remove file if it exists, sessionVars if valid
			if(!is_string($id)) {
				throw new A\Exception('Session id is not valid ('.$id.')');
			}
			else {				
				if($this->hasSessionTimeExpire($creationTime)) {
					return false;
				}
				else {
					if(($this->opt('navigatorCheck') AND $navigator != $_SERVER["HTTP_USER_AGENT"])
					   OR ($this->opt('langsCheck') AND $langs AND $langs != A\Util::getIfset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
					   OR ($this->opt('ipCheck') AND $ip != $_SERVER["REMOTE_ADDR"])) {
						
						return false;
					}
					else {
						return true;
					}
				}
			}
		}
		
		/**
		* Generate a new id for the current session
		*/
		public function regenerateId() {
			$this->removeSession($this->sessId);
			
			$this->newSession($this->_vars, true);
		}
		
		/**
		* Destroy current session (reset it and change id)
		*/
		public function destroy() {
			$this->removeSession($this->sessId);
			
			$this->newSession();
		}
		
		############### READ / WRITE METHODS ##########################
		
		abstract protected function saveSession($id, $data);
		
		abstract protected function getSessionData($id);
		
		/**
		* Remove a session
		*/
		abstract protected function removeSession($id);
		
		/**
		* Return all sessions data like array(array('creationTime' => 54646654, 'ip' => ...), array('creationTime' => ...))
		*/
		abstract protected function getAllSessions();
		
		/**
		* Check if it is time to clean session folder (or other storage way)
		*/
		abstract protected function checkClean();
		
		abstract protected function clean();
		
		abstract protected function sessionIdExists($id);
		
	}
	
?>